package com.zhp.keyresolver.impl;

import cn.hutool.core.util.ArrayUtil;
import com.zhp.anno.Idempotent;
import com.zhp.keyresolver.IdempotentKeyResolver;
import org.aspectj.lang.JoinPoint;
import org.aspectj.lang.reflect.MethodSignature;
import org.springframework.core.LocalVariableTableParameterNameDiscoverer;
import org.springframework.core.ParameterNameDiscoverer;
import org.springframework.expression.EvaluationContext;
import org.springframework.expression.Expression;
import org.springframework.expression.ExpressionParser;
import org.springframework.expression.spel.standard.SpelExpressionParser;
import org.springframework.expression.spel.support.StandardEvaluationContext;

import java.lang.reflect.Method;

/**
 * spel 组装key
 *
 * @author zhanghp
 * @since 2024/5/27 13:57
 */
public class ExpressionIdempotentKeyResolver implements IdempotentKeyResolver {

    /**
     * 方法参数名称解析
     */
    private final ParameterNameDiscoverer parameterNameDiscoverer =new LocalVariableTableParameterNameDiscoverer();

    /**
     * spel 解析器
     */
    private final ExpressionParser parser = new SpelExpressionParser();

    @Override
    public String assembleKey(JoinPoint joinPoint, Idempotent idempotent) {
        Method method = getMethod(joinPoint);
        Object[] args = joinPoint.getArgs();
        String[] parameterNames = parameterNameDiscoverer.getParameterNames(method);

        //EvaluationContext 接口在评估表达式以解析属性、方法或字段时使用，并帮助执行类型转换。Spring提供了两种实现。
        EvaluationContext context = new StandardEvaluationContext();
        if (ArrayUtil.isNotEmpty(parameterNames)) {
            for (int i = 0; i < parameterNames.length; i++) {
                context.setVariable(parameterNames[i], args[i]);
            }
        }
        // spel 解析器解析keyArg
        Expression expression = parser.parseExpression(idempotent.keyArg());
        // 将EvaluationContext传入，获取解析的value类型
        return expression.getValue(context, String.class);
    }

    private static Method getMethod(JoinPoint point) {
        // 处理，声明在类上的情况
        MethodSignature signature = (MethodSignature) point.getSignature();
        Method method = signature.getMethod();
        if (!method.getDeclaringClass().isInterface()) {
            return method;
        }

        // 处理，声明在接口上的情况
        try {
            return point.getTarget().getClass().getDeclaredMethod(
                    point.getSignature().getName(), method.getParameterTypes());
        } catch (NoSuchMethodException e) {
            throw new RuntimeException(e);
        }
    }
}
